bpf: Fix off-by-one for area size in creating mask to left
authorPiotr Krysiuk <piotras@gmail.com>
Tue, 16 Mar 2021 07:20:16 +0000 (08:20 +0100)
committerSalvatore Bonaccorso <carnil@debian.org>
Fri, 19 Mar 2021 18:20:52 +0000 (18:20 +0000)
Origin: https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf.git/commit?id=10d2bb2e6b1d8c4576c56a748f697dbeb8388899
Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2020-27171

retrieve_ptr_limit() computes the ptr_limit for registers with stack and
map_value type. ptr_limit is the size of the memory area that is still
valid / in-bounds from the point of the current position and direction
of the operation (add / sub). This size will later be used for masking
the operation such that attempting out-of-bounds access in the speculative
domain is redirected to remain within the bounds of the current map value.

When masking to the right the size is correct, however, when masking to
the left, the size is off-by-one which would lead to an incorrect mask
and thus incorrect arithmetic operation in the non-speculative domain.
Piotr found that if the resulting alu_limit value is zero, then the
BPF_MOV32_IMM() from the fixup_bpf_calls() rewrite will end up loading
0xffffffff into AX instead of sign-extending to the full 64 bit range,
and as a result, this allows abuse for executing speculatively out-of-
bounds loads against 4GB window of address space and thus extracting the
contents of kernel memory via side-channel.

Fixes: 979d63d50c0c ("bpf: prevent out of bounds speculation on pointer arithmetic")
Signed-off-by: Piotr Krysiuk <piotras@gmail.com>
Co-developed-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Acked-by: Alexei Starovoitov <ast@kernel.org>
Gbp-Pq: Topic bugfix/all
Gbp-Pq: Name bpf-Fix-off-by-one-for-area-size-in-creating-mask-to.patch

kernel/bpf/verifier.c

index 1c2ecf23fefbdfcbc8af0d868df440ccf0353880..be716ef63986e9223ad33a6e33eaf359bc7172d6 100644 (file)
@@ -5342,13 +5342,13 @@ static int retrieve_ptr_limit(const struct bpf_reg_state *ptr_reg,
                 */
                off = ptr_reg->off + ptr_reg->var_off.value;
                if (mask_to_left)
-                       *ptr_limit = MAX_BPF_STACK + off;
+                       *ptr_limit = MAX_BPF_STACK + off + 1;
                else
                        *ptr_limit = -off;
                return 0;
        case PTR_TO_MAP_VALUE:
                if (mask_to_left) {
-                       *ptr_limit = ptr_reg->umax_value + ptr_reg->off;
+                       *ptr_limit = ptr_reg->umax_value + ptr_reg->off + 1;
                } else {
                        off = ptr_reg->smin_value + ptr_reg->off;
                        *ptr_limit = ptr_reg->map_ptr->value_size - off;